]> dgit.raspbian.org Git - gnome-shell.git/commitdiff
quickSettings: Add background apps menu
authorGeorges Basile Stavracas Neto <georges.stavracas@gmail.com>
Thu, 2 Feb 2023 18:58:43 +0000 (15:58 -0300)
committerMarge Bot <marge-bot@gnome.org>
Sun, 12 Feb 2023 16:16:36 +0000 (16:16 +0000)
Sandboxed apps that run without a window are detected by the new
background monitoring service, introduced by xdg-desktop-portal.

We have an opportunity to improve the predictability of the desktop
and ensure that application state in transparently reported to users
by showing these apps, and allowing them to closed.

Add a new background apps menu to the quick settings, that is always
added at the bottom of the popover, and has a slightly custom, flat
style applied to it.

Show background-running apps in this menu, and allow closing them
by first attempting to execute the 'quit' action through D-Bus, and
if that fails, sending SIGKILL to the process.

See https://gitlab.gnome.org/Teams/Design/os-mockups/-/issues/191

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2624>

data/dbus-interfaces/org.freedesktop.background.Monitor.xml [new file with mode: 0644]
data/gnome-shell-dbus-interfaces.gresource.xml
data/gnome-shell-icons.gresource.xml
data/icons/scalable/status/background-app-ghost-symbolic.svg [new file with mode: 0644]
data/theme/gnome-shell-sass/widgets/_quick-settings.scss
js/js-resources.gresource.xml
js/ui/panel.js
js/ui/status/backgroundApps.js [new file with mode: 0644]
po/POTFILES.in

diff --git a/data/dbus-interfaces/org.freedesktop.background.Monitor.xml b/data/dbus-interfaces/org.freedesktop.background.Monitor.xml
new file mode 100644 (file)
index 0000000..1a2028a
--- /dev/null
@@ -0,0 +1,64 @@
+<?xml version="1.0"?>
+<!--
+ Copyright (C) 2022 Endless OS Foundation, LLC
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+ Author: Georges Basile Stavracas Neto <georges@endlessos.org>
+-->
+
+<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
+  <!--
+    org.freedesktop.background.Monitor:
+    @short_description: Background applications monitor
+
+    This interface provides APIs related to applications
+    that are running in the background.
+
+    This documentation describes version 1 of this interface.
+  -->
+  <interface name="org.freedesktop.background.Monitor">
+
+    <!--
+      BackgroundApps:
+
+      The list of applications that are considered to be running in
+      background. The following keys are supported:
+
+      <variablelist>
+        <varlistentry>
+          <term>app_id s</term>
+          <listitem><para>
+            App id of the application.
+          </para></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term>instance s</term>
+          <listitem><para>
+            The Flatpak instance of the application.
+          </para></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term>message s</term>
+          <listitem><para>
+            Status message reported by the application. Optional.
+          </para></listitem>
+        </varlistentry>
+      </variablelist>
+    -->
+    <property name="BackgroundApps" type="aa{sv}" access="read"/>
+
+    <property name="version" type="u" access="read"/>
+  </interface>
+</node>
index 6682c462d6717380347625537eb6b92b2328ee45..31d1f126661218c80feb12389bf56228ce97d0a2 100644 (file)
@@ -6,6 +6,7 @@
     <file preprocess="xml-stripblanks">net.reactivated.Fprint.Device.xml</file>
     <file preprocess="xml-stripblanks">net.reactivated.Fprint.Manager.xml</file>
     <file preprocess="xml-stripblanks">org.freedesktop.Application.xml</file>
+    <file preprocess="xml-stripblanks">org.freedesktop.background.Monitor.xml</file>
     <file preprocess="xml-stripblanks">org.freedesktop.bolt1.Device.xml</file>
     <file preprocess="xml-stripblanks">org.freedesktop.bolt1.Manager.xml</file>
     <file preprocess="xml-stripblanks">org.freedesktop.DBus.xml</file>
index d808c9be68b3cb8e5650261231b65b7754875100..fb33b22dc552a4ddcdf872b1470cbbdd2c56f502 100644 (file)
@@ -18,6 +18,7 @@
     <file>scalable/actions/screenshot-ui-show-pointer-symbolic.svg</file>
     <file>scalable/actions/screenshot-ui-window-symbolic.svg</file>
     <file>scalable/actions/screenshot-recorded-symbolic.svg</file>
+    <file>scalable/status/background-app-ghost-symbolic.svg</file>
     <file>scalable/status/keyboard-caps-lock-symbolic.svg</file>
     <file>scalable/status/keyboard-enter-symbolic.svg</file>
     <file>scalable/status/keyboard-hide-symbolic.svg</file>
diff --git a/data/icons/scalable/status/background-app-ghost-symbolic.svg b/data/icons/scalable/status/background-app-ghost-symbolic.svg
new file mode 100644 (file)
index 0000000..213b601
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
+    <path d="m 7.992188 1 c -3.867188 0.003906 -6.996094 3.140625 -6.992188 7.007812 v 5.988282 c 0 0.554687 0.449219 1.003906 1.003906 1.003906 c 0.550782 -0.003906 1 -0.453125 0.996094 -1.003906 v -0.992188 c 0 -0.554687 0.449219 -1.003906 1.003906 -1.003906 c 0.550782 0.003906 1 0.453125 0.996094 1.003906 v 0.992188 c 0 0.554687 0.449219 1.003906 1.003906 1.003906 c 0.550782 -0.003906 1 -0.453125 0.996094 -1.003906 v -0.992188 c 0 -0.554687 0.449219 -1.003906 1.003906 -1.003906 c 0.550782 0.003906 1 0.453125 0.996094 1.003906 v 0.992188 c 0 0.554687 0.449219 1.003906 1.003906 1.003906 c 0.550782 -0.003906 1 -0.453125 0.996094 -1.003906 v -0.992188 c 0 -0.554687 0.449219 -1.003906 1.003906 -1.003906 c 0.550782 0.003906 1 0.453125 0.996094 1.003906 v 0.992188 c 0 0.554687 0.449219 1.003906 1.003906 1.003906 c 0.550782 -0.003906 1 -0.453125 0.996094 -1.003906 v -5.988282 c 0 -3.871093 -3.136719 -7.007812 -7.007812 -7.007812 z m -1.992188 4 c 0.554688 0 1 0.445312 1 1 v 1 c 0 0.554688 -0.445312 1 -1 1 s -1 -0.445312 -1 -1 v -1 c 0 -0.554688 0.445312 -1 1 -1 z m 4 0 c 0.554688 0 1 0.445312 1 1 v 1 c 0 0.554688 -0.445312 1 -1 1 s -1 -0.445312 -1 -1 v -1 c 0 -0.554688 0.445312 -1 1 -1 z m 0 0" fill="#222222"/>
+</svg>
index 0064aeed30bd9e6a4dfcd5d33f1e25a449506c79..6a4c42d9ab7daea0b8642cc79356da8ad6105275 100644 (file)
 }
 
 .device-subtitle { color: transparentize($fg_color, 0.5); }
+
+// background apps
+
+.background-apps-quick-toggle {
+  min-height: 40px;
+  background-color: transparent;
+
+  & StIcon { icon-size: $base_icon_size !important; }
+}
+
+.background-app-item {
+  & .title { @extend %heading; }
+  & .subtitle { @extend %caption; }
+  & .popup-menu-icon {
+    icon-size: $large_icon_size !important;
+    -st-icon-style: regular !important;
+  }
+  & .close-button {
+    @extend .icon-button;
+    padding: $base_padding;
+  }
+
+  &.popup-inactive-menu-item { color: $fg_color; }
+}
index 4213081d20f8bfff19150fccbde2f412bae1fc64..ba481aafa8af637440bb3e178404e6196b450c10 100644 (file)
 
     <file>ui/status/accessibility.js</file>
     <file>ui/status/autoRotate.js</file>
+    <file>ui/status/backgroundApps.js</file>
     <file>ui/status/brightness.js</file>
     <file>ui/status/darkMode.js</file>
     <file>ui/status/dwellClick.js</file>
index 0dd62561f21ea00f800c23beda60cc4b1f71a48d..19a28dedc0b4569134dc0c7af0bfa841fbf32f9e 100644 (file)
@@ -366,6 +366,7 @@ class QuickSettings extends PanelMenu.Button {
         this._rfkill = new imports.ui.status.rfkill.Indicator();
         this._autoRotate = new imports.ui.status.autoRotate.Indicator();
         this._unsafeMode = new UnsafeModeIndicator();
+        this._backgroundApps = new imports.ui.status.backgroundApps.Indicator();
 
         this._indicators.add_child(this._brightness);
         this._indicators.add_child(this._remoteAccess);
@@ -401,6 +402,8 @@ class QuickSettings extends PanelMenu.Button {
         this._addItems(this._rfkill.quickSettingsItems);
         this._addItems(this._autoRotate.quickSettingsItems);
         this._addItems(this._unsafeMode.quickSettingsItems);
+
+        this._addItems(this._backgroundApps.quickSettingsItems, N_QUICK_SETTINGS_COLUMNS);
     }
 
     _addItems(items, colSpan = 1) {
diff --git a/js/ui/status/backgroundApps.js b/js/ui/status/backgroundApps.js
new file mode 100644 (file)
index 0000000..606fa33
--- /dev/null
@@ -0,0 +1,225 @@
+/* exported Indicator */
+const {Clutter, Gio, GLib, GObject, Shell, St} = imports.gi;
+
+const Main = imports.ui.main;
+const PopupMenu = imports.ui.popupMenu;
+const Util = imports.misc.util;
+
+const {QuickToggle, SystemIndicator} = imports.ui.quickSettings;
+const {loadInterfaceXML} = imports.misc.dbusUtils;
+
+const DBUS_NAME = 'org.freedesktop.background.Monitor';
+const DBUS_OBJECT_PATH = '/org/freedesktop/background/monitor';
+
+const BackgroundMonitorIface = loadInterfaceXML('org.freedesktop.background.Monitor');
+const BackgroundMonitorProxy = Gio.DBusProxy.makeProxyWrapper(BackgroundMonitorIface);
+
+Gio._promisify(Gio.DBusConnection.prototype, 'call');
+
+var BackgroundAppMenuItem = GObject.registerClass({
+    Properties: {
+        'app': GObject.ParamSpec.object('app', '', '',
+            GObject.ParamFlags.READWRITE,
+            Shell.App),
+        'instance': GObject.ParamSpec.int64('instance', '', '',
+            GObject.ParamFlags.READWRITE,
+            -1, GLib.MAXINT64_BIGINT, -1),
+        'message': GObject.ParamSpec.string('message', '', '',
+            GObject.ParamFlags.READWRITE,
+            null),
+    },
+}, class BackgroundAppMenuItem extends PopupMenu.PopupImageMenuItem {
+    _init(app, params = {}) {
+        const message = params.message;
+        delete params.message;
+
+        const instance = params.instance;
+        delete params.instance;
+
+        super._init(app.get_name(), app.get_icon(), {
+            activate: false,
+            reactive: false,
+            ...params,
+        });
+
+        this.set({message, instance});
+
+        this.add_style_class_name('background-app-item');
+        this.label.add_style_class_name('title');
+
+        this.app = app;
+
+        const box = new St.BoxLayout({
+            vertical: true,
+            x_expand: true,
+            x_align: Clutter.ActorAlign.START,
+            y_align: Clutter.ActorAlign.CENTER,
+        });
+        this.add_child(box);
+
+        this.remove_child(this.label);
+        box.add_child(this.label);
+
+        const messageLabel = new St.Label({
+            y_expand: true,
+            y_align: Clutter.ActorAlign.CENTER,
+            style_class: 'subtitle',
+        });
+        box.add_child(messageLabel);
+
+        this.bind_property('message',
+            messageLabel, 'text', GObject.BindingFlags.SYNC_CREATE);
+        this.bind_property_full('message',
+            messageLabel, 'visible', GObject.BindingFlags.SYNC_CREATE,
+            (bind, source) => [true, source !== null],
+            null);
+
+        this.set_child_above_sibling(this._ornamentLabel, null);
+
+        const closeButton = new St.Button({
+            iconName: 'window-close-symbolic',
+            styleClass: 'close-button',
+            x_expand: true,
+            y_expand: false,
+            x_align: Clutter.ActorAlign.END,
+            y_align: Clutter.ActorAlign.CENTER,
+        });
+        this.add_child(closeButton);
+
+        closeButton.connect('clicked', () => this._quitApp().catch(logError));
+    }
+
+    async _quitApp() {
+        const appId = this.app.get_id().replace(/\.desktop$/, '');
+
+        try {
+            await Gio.DBus.session.call(
+                appId,
+                `/${appId.replaceAll('.', '/')}`,
+                'org.freedesktop.Application',
+                'ActivateAction',
+                new GLib.Variant('(sava{sv})', ['quit', [], {}]),
+                null,
+                Gio.DBusCallFlags.NONE,
+                -1,
+                null);
+        } catch (_error) {
+            try {
+                Util.trySpawn(['flatpak', 'kill', this.instance]);
+            } catch (pidError) {
+                logError(pidError, 'Failed to kill application');
+            }
+        }
+    }
+});
+
+const BackgroundAppsToggle = GObject.registerClass(
+class BackgroundAppsToggle extends QuickToggle {
+    _init() {
+        super._init({
+            visible: false,
+            hasMenu: true,
+            // The background apps toggle looks like a flat menu, but doesn't
+            // have a separate menu button. Fake it with an arrow icon.
+            iconName: 'go-next-symbolic',
+        });
+
+        this.add_style_class_name('background-apps-quick-toggle');
+
+        this._box.set_child_above_sibling(this._icon, null);
+
+        this._appSystem = Shell.AppSystem.get_default();
+
+        this.menu.setHeader(
+            'background-app-ghost-symbolic',
+            C_('title', 'Background Apps'));
+
+        new BackgroundMonitorProxy(
+            Gio.DBus.session,
+            DBUS_NAME,
+            DBUS_OBJECT_PATH,
+            proxy => {
+                this._proxy = proxy;
+                proxy?.connect('g-properties-changed', () => this._sync());
+                this._sync();
+            },
+            null,
+            Gio.DBusProxyFlags.DO_NOT_AUTO_START);
+
+        this._appsSection = new PopupMenu.PopupMenuSection();
+        this.menu.addMenuItem(this._appsSection);
+
+        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
+        this.menu.addSettingsAction(_('App Settings'),
+            'gnome-applications-panel.desktop');
+
+        this.connect('popup-menu', () => this.menu.open());
+
+        this.menu.connect('open-state-changed', () => this._syncVisibility());
+        Main.sessionMode.connect('updated', () => this._syncVisibility());
+    }
+
+    _syncVisibility() {
+        const {isLocked} = Main.sessionMode;
+        const nBackgroundApps = this._proxy?.BackgroundApps?.length;
+        // We cannot hide the quick toggle while the menu is open, otherwise
+        // the menu position goes bogus. We can't show it in locked sessions
+        // either
+        this.visible = !isLocked && (this.menu.isOpen || nBackgroundApps > 0);
+    }
+
+    _sync() {
+        this._syncVisibility();
+
+        if (!this._proxy)
+            return;
+
+        const {BackgroundApps: backgroundApps} = this._proxy;
+
+        const nBackgroundApps = backgroundApps?.length ?? 0;
+        this.title = nBackgroundApps === 0
+            ? _('No Background Apps')
+            : ngettext(
+                '%d Background App',
+                '%d Background Apps',
+                nBackgroundApps).format(nBackgroundApps);
+
+        this._appsSection.removeAll();
+
+        if (!backgroundApps)
+            return;
+
+        backgroundApps
+            .map(backgroundApp => {
+                const appId = backgroundApp.app_id.deepUnpack();
+                const app = this._appSystem.lookup_app(`${appId}.desktop`);
+                const message = backgroundApp.message?.deepUnpack();
+                const instance = backgroundApp.instance.deepUnpack();
+
+                return {app, message, instance};
+            })
+            .sort((a, b) => {
+                return a.app.get_name().localeCompare(b.app.get_name());
+            })
+            .forEach(backgroundApp => {
+                const {app, message, instance} = backgroundApp;
+
+                const item = new BackgroundAppMenuItem(app,
+                    {instance, message});
+                this._appsSection.addMenuItem(item);
+            });
+    }
+
+    vfunc_clicked() {
+        this.menu.open();
+    }
+});
+
+var Indicator = GObject.registerClass(
+class Indicator extends SystemIndicator {
+    _init() {
+        super._init();
+
+        this.quickSettingsItems.push(new BackgroundAppsToggle());
+    }
+});
index 5ceecebcbde2257eb5ff0c86b45c2e126b5bdaed..88409eb95c050fd3a624c01dd131caadb565080c 100644 (file)
@@ -58,6 +58,7 @@ js/ui/shellEntry.js
 js/ui/shellMountOperation.js
 js/ui/status/accessibility.js
 js/ui/status/autoRotate.js
+js/ui/status/backgroundApps.js
 js/ui/status/bluetooth.js
 js/ui/status/brightness.js
 js/ui/status/darkMode.js